<?php
/*
 * MiniMe - a minimalistic OStatus complient microblogging tool
 * Copyright (C) 2010, Tuomas Koski - koski.tuomas@gmail.com
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/**
 * REFACTOR THE LOGIC IN THIS CLASS!!
 * 
 * @author     Tuomas Koski <koski.tuomas@gmail.com>
 * @copyright  Tuomas Koski
 * @license    http://www.gnu.org/licenses/lgpl.txt
 */
class Atom_Entry implements Atom_ChildElement {
    
    const ELEMENT_ENTRY    = "entry";
        
    private $namespaces = array();
    
    /**
     * @var Array of Atom_ChildElement
     */
    private $childElements = array();
    
    public function __construct() {

    }
    
    public function addChildElement(Atom_ChildElement $childElement) {
        $this->childElements[] = $childElement;
    }
    
    public function addNamespace($namespaceURI, $qualifiedName) {
        $this->namespaces['xmlns:' . $namespaceURI] = $qualifiedName;
    }
    
    /**
     * @return Atom_Entry_Author
     */
    public function getAuthor() {
        
        foreach ($this->childElements as $elm) {
            if($elm instanceof Atom_Entry_Author) {
                return $elm;
            }
        }
        return null;
    }
    
    /**
     * @return Atom_Entry_Updated
     */
    public function getUpdated() {
        
        foreach ($this->childElements as $elm) {
            if($elm instanceof Atom_Entry_Updated) {
                return $elm;
            }
        }
        return null;
    }
    
	/**
     * @return Atom_Entry_Link_Enclosure
     */
    public function getEnclosure() {
        
        foreach ($this->childElements as $elm) {
            if($elm instanceof Atom_Entry_Link_Enclosure) {
                return $elm;
            }
        }
        return null;
    }
    
	/**
     * @return Atom_Entry_Title
     */
    public function getTitle() {
        
        foreach ($this->childElements as $elm) {
            if($elm instanceof Atom_Entry_Title) {
                return $elm;
            }
        }
        return null;
    }
    
	/**
     * @return Atom_Entry_Id
     */
    public function getId() {
        
        foreach ($this->childElements as $elm) {
            if($elm instanceof Atom_Entry_Id) {
                return $elm;
            }
        }
        return null;
    }
    
	/**
     * @return Atom_Entry_Content
     */
    public function getContent() {
        
        foreach ($this->childElements as $elm) {
            if($elm instanceof Atom_Entry_Content) {
                return $elm;
            }
        }
        return null;
    }
    
    /**
     * @return Activitystream_Verb
     */
    public function getActivitystreamVerb() {
        
        foreach ($this->childElements as $elm) {
            if($elm instanceof Activitystream_Verb) {
                return $elm;
            }
        }
        return null;
    }
    
    /**
     * @return Thr_InReplyTo
     */
    public function getInReplyTo() {
        
        foreach ($this->childElements as $elm) {
            if($elm instanceof Thr_InReplyTo) {
                return $elm;
            }
        }
        return null;
    }
    
    public function asXML() {
        $dom = new DOMDocument('1.0', 'UTF-8');
        $dom->formatOutput = true;
        
        $entry_element = $dom->createElementNS(Atom::ATOM_NS, self::ELEMENT_ENTRY);

        foreach ($this->namespaces as $namespaceURI => $qualifiedName) {
            $entry_element->setAttributeNS('http://www.w3.org/2000/xmlns/', $namespaceURI, $qualifiedName);
        }
        
        foreach ($this->childElements as $childElement) {
            $entry_element->appendChild($childElement->asDomElement($dom));
        } 
        
        $dom->appendChild($entry_element);
        
        return $dom->saveXML();	    
    }
    
	/**
     * @return DOMElement
     */
    public function asDomElement(DOMDocument $dom) {
        $entry_element = $dom->createElementNS(Atom::ATOM_NS, self::ELEMENT_ENTRY);

        foreach ($this->namespaces as $namespaceURI => $qualifiedName) {
            $entry_element->setAttributeNS('http://www.w3.org/2000/xmlns/', $namespaceURI, $qualifiedName);
        }
        
        foreach ($this->childElements as $childElement) {
            $entry_element->appendChild($childElement->asDomElement($dom));
        } 
        
        return $entry_element;
    }
    
    /**
     * 
     * @param String $rawXml
     * @return Atom_Entry
     */
    public static function buildFromRawXML($rawXml) {
        $dom = new DOMDocument();
        if(@$dom->loadXML($rawXml)) {
            return self::buildFromDom($dom);
        }
        throw new Exception("Invalid raw XML data provided!");
    }
    
    /**
     * <?xml version="1.0" encoding="UTF-8"?>
     * <entry xmlns="http://www.w3.org/2005/Atom" xmlns:activity="http://activitystrea.ms/spec/1.0/">
     *   <id>mimime:1280844851</id>
     *   <title>User C is now following user-b@localhost</title>
     *   <content type="html">&lt;a href='http://localhost/index.php?controller=profile&amp;username=user-c'&gt;User C is now following user-b@localhost</content>
     *   <author>
     *     <uri>acct:user-c@localhost</uri>
     *     <name>User C</name>
     *   </author>
     *   <activity:actor>
     *     <activity:object-type>http://activitystrea.ms/schema/1.0/person</activity:object-type>
     *     <id>http://localhost/index.php?controller=profile&amp;username=user-c</id>
     *     <title>User C</title>
     *     <link rel="alternate" type="text/html" href="http://localhost/index.php?controller=profile&amp;username=user-c"/>
     *   </activity:actor>
     *   <activity:verb>http://activitystrea.ms/schema/1.0/follow</activity:verb>
     * </entry>
     *
     * 
     * This logic is terrible. Fix this.
     * 
     * @param DOMDocument $dom
     * @return Atom_Entry
     */
    public static function buildFromDom(DOMDocument $dom) {
        
        $entry_element = $dom->getElementsByTagName(self::ELEMENT_ENTRY)->item(0);
        if (!$entry_element) {
            throw new Exception("Invalid DOM object for Atom_Entry, missing entry element");
        }
        
        return self::buildFromDOMElement($entry_element);
    }
    
	/**
     * @param DOMElement $elm
     * @return Atom_Entry
     */
    public static function buildFromDOMElement(DOMElement $elm) {
        
        try {
            $atom = new Atom_Entry();
            
            $id = Atom_Entry_Id::buildFromDOMElement(self::getFirstElementByTagName($elm, Atom_Entry_Id::ELEMENT_ID));
            $atom->addChildElement($id);
    
            $author = self::getFirstElementByTagName($elm, Atom_Entry_Author::ELEMENT_AUTHOR);
            if($author !== null) {
                $author = Atom_Entry_Author::buildFromDOMElement($author);
                $atom->addChildElement($author);
            }
            
            foreach ($elm->getElementsByTagName(Atom_Entry_Link::ELEMENT_LINK) as $elmLink) {
                        
                // this is quite a hack.
                if( $elmLink->parentNode->nodeName !== self::ELEMENT_ENTRY) {
                    continue;
                }
                
                if($elmLink->getAttribute(Atom_Entry_Link_AlternateHtml::ATTRIBUTE_REL) == Atom_Entry_Link_AlternateHtml::REL_VALUE) {
                    $link = Atom_Entry_Link_AlternateHtml::buildFromDOMElement($elmLink); 
                    $atom->addChildElement($link);
                }
                
                if($elmLink->getAttribute(Atom_Entry_Link_Enclosure::ATTRIBUTE_REL) == Atom_Entry_Link_Enclosure::REL_VALUE) {
                    $link = Atom_Entry_Link_Enclosure::buildFromDOMElement($elmLink); 
                    $atom->addChildElement($link);
                }
            }
            
            $updated = self::getFirstElementByTagName($elm, Atom_Entry_Updated::ELEMENT_UPDATED);
            if($updated !== null) {
                $updated = Atom_Entry_Updated::buildFromDOMElement($updated);
                $atom->addChildElement($updated);
            } else {
                $atom->addChildElement(new Atom_Entry_Updated());
            }
            
            $title = self::getFirstElementByTagName($elm, Atom_Entry_Title::ELEMENT_TITLE);
            if($title != null) {
                $title = Atom_Entry_Title::buildFromDOMElement($title);
                $atom->addChildElement($title);
            }
            
            $content = self::getFirstElementByTagName($elm, Atom_Entry_Content::ELEMENT_CONTENT);
            if($content !== null) {
                $content = Atom_Entry_Content::buildFromDOMElement($content);
                $atom->addChildElement($content);
            }
            
            if($title == null && $content !== null) {
                $atom->addChildElement(new Atom_Entry_Title($content->getContent()));
            } 
            
            $actor = self::getFirstElementByTagNameNS($elm, ActivityStream::AS_NS, Activitystream_Actor::ELEMENT_ACTOR);
            if($actor != null) {
                $actor = Activitystream_Actor::buildFromDOMElement($actor);
                $atom->addChildElement($actor);
            }
            
            $verb = self::getFirstElementByTagNameNS($elm, ActivityStream::AS_NS, Activitystream_Verb::ELEMENT_VERB);
            if($verb != null) {
                $verb = Activitystream_Verb::buildFromDOMElement($verb);
                $atom->addChildElement($verb);
            }
            
            $object = self::getFirstElementByTagNameNS($elm, ActivityStream::AS_NS, Activitystream_Object::ELEMENT_OBJECT);
            if($object != null) {
                $object = Activitystream_Object::buildFromDOMElement($object);
                $atom->addChildElement($object);
            }
            
            $inReplyTo = self::getFirstElementByTagNameNS($elm, Thr::NS, Thr_InReplyTo::ELEMENT_IN_REPLY_TO);
            if($inReplyTo != null) {
                $inReplyTo = Thr_InReplyTo::buildFromDOMElement($inReplyTo);
                $atom->addChildElement($inReplyTo);
                $atom->addNamespace(Thr::NS_PREFIX, Thr::NS);
            }
            
            return $atom;
        } catch (Exception $e) {
            // TODO add log here when we have the logger done.
        }
        return null;
    }
    
    public static function buildFromArray(Array $array) {
        $atom = new Atom_Entry();
        
        $id = new Atom_Entry_Id($array['id']);
        $atom->addChildElement($id);
        
        $updated = new Atom_Entry_Updated($array['updated']);
        $atom->addChildElement($updated);
        
        $link = new Atom_Entry_Link_AlternateHtml($array['alternate_href']); 
        $atom->addChildElement($link);
        
        $title = new Atom_Entry_Title($array['title']);
        $atom->addChildElement($title);
        
        $content = new Atom_Entry_Content($array['title'], Atom_Entry_Content::TYPE_HTML);
        $atom->addChildElement($content);
        
        $content = Atom_Entry_Author::buildFromArray($array['author']);
        $atom->addChildElement($content);
        
        if(isset($array['in-reply-to'])) {
            $inReplyTo = new Thr_InReplyTo($array['in-reply-to']['rel'], $array['in-reply-to']['href']);
            $atom->addChildElement($inReplyTo);
            $atom->addNamespace(Thr::NS_PREFIX, Thr::NS);
        }
        
        return $atom;
    }
    
    /**
     * Terrible method. fix this.
     * @param unknown_type $elm
     * @param unknown_type $childElementName
     * @return unknown_type
     */
    public static function getFirstElementByTagName($elm, $childElementName) {
        $elements = $elm->getElementsByTagName($childElementName);
        if($elements->length === 0) {
            return null;
        }
        return $elements->item(0);
    }
    
    /**
     * Terrible method. fix this.
     * @param unknown_type $elm
     * @param unknown_type $childElementName
     * @return unknown_type
     */
    public static function getFirstElementByTagNameNS($elm, $ns, $childElementName) {
        $elements = $elm->getElementsByTagNameNS($ns, $childElementName);
        if($elements->length === 0) {
            return null;
        }
        return $elements->item(0);
    }
    
    public function __toString() {
        return $this->asXML();
    }
    
}